﻿//-----------------------------------------------------------------------
// <copyright file="MainPage.cs" company="Microsoft Corporation">
//     Copyright Microsoft Corporation. All Rights Reserved. This code released under the terms of the Microsoft Public License (MS-PL, http://opensource.org/licenses/ms-pl.html.) This is sample code only, do not use in production environments.
// </copyright>
//-----------------------------------------------------------------------
namespace vsarFlightPlan.ViewModels
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.Diagnostics;
    using System.Globalization;
    using System.IO;
    using System.Linq;
    using System.Runtime.InteropServices.WindowsRuntime;
    using System.Threading.Tasks;
    using System.Windows.Input;
    using System.Xml;
    using System.Xml.Linq;
    using VisualStudioOnlineOData;
    using VisualStudioOnlineOData.Models;
    using VisualStudioOnlineOData.Utilities;
    using vsarFlightPlan.Utilities;
    using Windows.ApplicationModel.Background;
    using Windows.Graphics.Imaging;
    using Windows.Storage;
    using Windows.Storage.Streams;
    using Windows.System;
    using Windows.UI.Core;
    using Windows.UI.Popups;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Media.Imaging;

    /// <summary>
    ///
    /// </summary>
    internal class MainPage : ViewModelBase
    {
        private DateTime _lastUpdated;
        private bool _loading;
        private bool _metadataEditor;
        private bool _passwordPrompt;
        private string _search;
        private DetailedViewItem _selectedItem;
        private bool _showAllItems;
        private bool _showSettingsPrompt;
        private bool _showUpdating;
        private string _userProvidedPassword;
        private List<DetailedViewItem> allItems = new List<DetailedViewItem>();
        private Credentials credentials;
        private string QueryId;
        private ApplicationDataContainer settings;

        private bool showLegend;

        /// <summary>
        /// Initializes a new instance of the <see cref="MainPage"/> class.
        /// </summary>
        public MainPage()
        {
            Loading = true;
            this.settings = SettingUtils.SettingsContainer();
            ShowAllItems = false;
            DetailedItems = new ObservableCollection<DetailedViewItem>();
            PasswordProvided = new Command(async () => { await OnPasswordProvided(); });
            GoToSettings = new Command(() => OnGoToSettings());
            EditMetadata = new Command(item => OnEditMetadata(item));
            UpdateMetadata = new Command(async item => { await OnUpdateMetadata(item); });
            ToggleHiddenItems = new Command(() => OnToggleHiddenItems());
            ToggleHideStatus = new Command(item => OnToggleHideStatus(item));
            Refresh = new Command(async () => { await OnRefresh(); });
            ClearCache = new Command(() => { var __ = OnClearCache(); });
            OpenLegend = new Command(() => this.OnOpenLegend());
            CloseLegend = new Command(() => this.OnCloseLegend());
        }

        /// <summary>
        /// Gets the detailed items.
        /// </summary>
        /// <value>
        /// The detailed items.
        /// </value>
        public ObservableCollection<DetailedViewItem> DetailedItems { get; private set; }

        /// <summary>
        /// Gets the edit metadata.
        /// </summary>
        /// <value>
        /// The edit metadata.
        /// </value>
        public ICommand EditMetadata { get; private set; }

        /// <summary>
        /// Gets the go to settings.
        /// </summary>
        /// <value>
        /// The go to settings.
        /// </value>
        public ICommand GoToSettings { get; private set; }

        /// <summary>
        /// Gets or sets the last updated.
        /// </summary>
        /// <value>
        /// The last updated.
        /// </value>
        public DateTime LastUpdated
        {
            get
            {
                return _lastUpdated;
            }
            set
            {
                this.UpdateProperty(ref _lastUpdated, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [loading].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [loading]; otherwise, <c>false</c>.
        /// </value>
        public bool Loading
        {
            get
            {
                return _loading;
            }
            set
            {
                this.UpdateProperty(ref _loading, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [metadata editor].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [metadata editor]; otherwise, <c>false</c>.
        /// </value>
        public bool MetadataEditor
        {
            get
            {
                return _metadataEditor;
            }
            set
            {
                this.UpdateProperty(ref _metadataEditor, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [password prompt].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [password prompt]; otherwise, <c>false</c>.
        /// </value>
        public bool PasswordPrompt
        {
            get
            {
                return _passwordPrompt;
            }
            set
            {
                this.UpdateProperty(ref _passwordPrompt, value);
            }
        }

        public bool ShowLegend
        {
            get
            {
                return this.showLegend;
            }
            set
            {
                this.UpdateProperty(ref showLegend, value);
            }
        }

        /// <summary>
        /// Gets the password provided.
        /// </summary>
        /// <value>
        /// The password provided.
        /// </value>
        public ICommand PasswordProvided { get; private set; }

        /// <summary>
        /// Gets the refresh.
        /// </summary>
        /// <value>
        /// The refresh.
        /// </value>
        public ICommand Refresh { get; private set; }

        public ICommand ClearCache { get; private set; }

        public ICommand OpenLegend { get; private set; }

        public ICommand CloseLegend { get; private set; }

        public string Search
        {
            get
            {
                return _search;
            }
            set
            {
                if (this.UpdateProperty(ref _search, value))
                {
                    UpdateDisplayItems();
                }
            }
        }

        /// <summary>
        /// Gets or sets the selected item.
        /// </summary>
        /// <value>
        /// The selected item.
        /// </value>
        public DetailedViewItem SelectedItem
        {
            get
            {
                return _selectedItem;
            }
            set
            {
                this.UpdateProperty(ref _selectedItem, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [show all items].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [show all items]; otherwise, <c>false</c>.
        /// </value>
        public bool ShowAllItems
        {
            get
            {
                return _showAllItems;
            }
            set
            {
                this.UpdateProperty(ref _showAllItems, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [show settings prompt].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [show settings prompt]; otherwise, <c>false</c>.
        /// </value>
        public bool ShowSettingsPrompt
        {
            get
            {
                return _showSettingsPrompt;
            }
            set
            {
                this.UpdateProperty(ref _showSettingsPrompt, value);
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [show updating].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [show updating]; otherwise, <c>false</c>.
        /// </value>
        public bool ShowUpdating
        {
            get
            {
                return _showUpdating;
            }
            set
            {
                this.UpdateProperty(ref _showUpdating, value);
            }
        }

        /// <summary>
        /// Gets the statuses.
        /// </summary>
        /// <value>
        /// The statuses.
        /// </value>
        public IEnumerable<string> Statuses
        {
            get
            {
                return Enum.GetNames(typeof(ProjectStatus));
            }
        }

        /// <summary>
        /// Gets the toggle hidden items.
        /// </summary>
        /// <value>
        /// The toggle hidden items.
        /// </value>
        public ICommand ToggleHiddenItems { get; private set; }

        /// <summary>
        /// Gets the toggle hide status.
        /// </summary>
        /// <value>
        /// The toggle hide status.
        /// </value>
        public ICommand ToggleHideStatus { get; private set; }

        /// <summary>
        /// Gets the update metadata.
        /// </summary>
        /// <value>
        /// The update metadata.
        /// </value>
        public ICommand UpdateMetadata { get; private set; }

        /// <summary>
        /// Gets or sets the user provided password.
        /// </summary>
        /// <value>
        /// The user provided password.
        /// </value>
        public string UserProvidedPassword
        {
            get
            {
                return _userProvidedPassword;
            }
            set
            {
                this.UpdateProperty(ref _userProvidedPassword, value);
            }
        }

        /// <summary>
        /// Loads and configures this instance.
        /// </summary>
        /// <returns></returns>
        public async Task Load()
        {
#if TABLET
            BackgroundTasks.Utilities.AttachTask("updater", TaskCompleted);
#endif
            await GetCachedWorkitems();
            this.credentials = GetCredentials();
            if (credentials == null)
            {
                Loading = false;

                ShowSettingsPrompt = true;
                return;
            }

            if (credentials.Password == null)
            {
                PasswordPrompt = true;
                Loading = false;
                return;
            }

            this.QueryId = settings.Read<string>("queryId", null);

            if (this.QueryId == null)
            {
                Loading = false;
                ShowSettingsPrompt = true;
                return;
            }

            await GetLatestWorkItems();
        }

        internal async Task SaveVisualElementToFile(FrameworkElement element)
        {
            var storageFolder = KnownFolders.PicturesLibrary;
            storageFolder = await storageFolder.CreateFolderAsync("Flight Plan Exports", CreationCollisionOption.OpenIfExists);
            var file = await storageFolder.CreateFileAsync("vsarFlightPlan-" + DateTime.Now.ToString("yyyyMMdd-HHmmss") + ".jpg");
            using (var stream = await file.OpenAsync(FileAccessMode.ReadWrite))
            {
                await SaveVisualElementToStream(element, stream);
            }

            var dialog = new MessageDialog("Flight Plan Image Saved as " + file.Name + " to Pictures Library.");
            await dialog.ShowAsync();
        }

        internal async Task SaveVisualElementToStream(FrameworkElement element, IRandomAccessStream stream)
        {
            var renderTargetBitmap = new RenderTargetBitmap();
            await renderTargetBitmap.RenderAsync(element, (int)element.Width, (int)element.Height);
            var pixels = await renderTargetBitmap.GetPixelsAsync();
            var encoder = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, stream);
            byte[] bytes = pixels.ToArray();
            encoder.SetPixelData(BitmapPixelFormat.Bgra8,
                                 BitmapAlphaMode.Ignore,
                                 (uint)element.ActualWidth, (uint)element.ActualHeight,
                                 96, 96, bytes);
            await encoder.FlushAsync();
        }

        /// <summary>
        /// Updates the display items.
        /// </summary>
        internal void UpdateDisplayItems()
        {
            var items = allItems.AsEnumerable();
            if (!string.IsNullOrWhiteSpace(Search))
            {
                items = items.Where(_ =>
                {
                    return ((!string.IsNullOrWhiteSpace(_.Description) && _.Description.IndexOf(Search, StringComparison.CurrentCultureIgnoreCase) > -1) ||
                            (!string.IsNullOrWhiteSpace(_.ProductOwner) && _.ProductOwner.IndexOf(Search, StringComparison.CurrentCultureIgnoreCase) > -1) ||
                            (!string.IsNullOrWhiteSpace(_.ProjectLead) && _.ProjectLead.IndexOf(Search, StringComparison.CurrentCultureIgnoreCase) > -1) ||
                            (!string.IsNullOrWhiteSpace(_.RuckMaster) && _.RuckMaster.IndexOf(Search, StringComparison.CurrentCultureIgnoreCase) > -1) ||
                            (!string.IsNullOrWhiteSpace(_.StatusString) && _.StatusString.IndexOf(Search, StringComparison.CurrentCultureIgnoreCase) > -1) ||
                            (!string.IsNullOrWhiteSpace(_.Title) && _.Title.IndexOf(Search, StringComparison.CurrentCultureIgnoreCase) > -1) ||
                            (!string.IsNullOrWhiteSpace(_.Version) && _.Version.IndexOf(Search, StringComparison.CurrentCultureIgnoreCase) > -1));
                });
            }

            if (!ShowAllItems)
            {
                // filter
                if (!settings.Read("ShowByDefaultCancelled", true))
                {
                    items = items.Where(_ => _.Status != ProjectStatus.Cancelled);
                }

                if (!settings.Read("ShowByDefaultDelayed", true))
                {
                    items = items.Where(_ => _.Status != ProjectStatus.Delayed);
                }

                if (!settings.Read("ShowByDefaultIdea", true))
                {
                    items = items.Where(_ => _.Status != ProjectStatus.Idea);
                }

                if (!settings.Read("ShowByDefaultInFlight", true))
                {
                    items = items.Where(_ => _.Status != ProjectStatus.InFlight);
                }

                if (!settings.Read("ShowByDefaultLanded", true))
                {
                    items = items.Where(_ => _.Status != ProjectStatus.Landed);
                }

                items = items.Where(_ => !_.Hidden);
            }

            items = items.OrderBy(_ => _.OriginalPosition);

            if (!DetailedItems.Any())
            {
                foreach (var item in items)
                {
                    DetailedItems.Add(item);
                }

                FilterLandedProjectsOutByBasedOnSettings();

                return;
            }

            foreach (var item in items)
            {
                if (!DetailedItems.Any(_ => _.Id == item.Id))
                {
                    var itemToInsertAfter = (from i in DetailedItems
                                             where i.OriginalPosition < item.OriginalPosition
                                             orderby i.OriginalPosition descending
                                             select i).FirstOrDefault();

                    if (itemToInsertAfter == null)
                    {
                        // doesn't exist on screen - add to start
                        DetailedItems.Insert(0, item);
                    }
                    else
                    {
                        DetailedItems.Insert(DetailedItems.IndexOf(itemToInsertAfter) + 1, item);
                    }
                }
            }

            var removeIds = new List<string>();
            foreach (var item in DetailedItems)
            {
                if (!items.Any(_ => _.Id == item.Id))
                {
                    // isn't in backing - so remove
                    removeIds.Add(item.Id);
                }
            }

            foreach (var item in removeIds)
            {
                DetailedItems.Remove(DetailedItems.Single(_ => _.Id == item));
            }
            FilterLandedProjectsOutByBasedOnSettings();
        }

        private static void OnGoToSettings()
        {
            (Window.Current.Content as Frame).Navigate(typeof(vsarFlightPlan.Settings));
        }

        private void CloseEditor()
        {
            MetadataEditor = false;
            Window.Current.CoreWindow.KeyUp -= EditorActiveCoreWindowKeyUp;
        }

        private async void EditorActiveCoreWindowKeyUp(CoreWindow sender, KeyEventArgs args)
        {
            if (MetadataEditor && SelectedItem != null)
            {
                if (args.VirtualKey == VirtualKey.Enter)
                {
                    await OnUpdateMetadata(SelectedItem);
                    args.Handled = true;
                }

                if (args.VirtualKey == VirtualKey.Escape)
                {
                    CloseEditor();
                    args.Handled = true;
                }
            }
            else
            {
                Window.Current.CoreWindow.KeyUp -= EditorActiveCoreWindowKeyUp;
                args.Handled = true;
            }
        }

        private void FilterLandedProjectsOutByBasedOnSettings()
        {
            var landedProjects = new Dictionary<string, DateTime?>();
            bool filterLandedProjectsByCount = settings.Read<bool>("FilterLandedProjectsByCount", false);
            int totalLandedProjectsToShow = settings.Read<int>("TotalLandedProjectsToShow", 3);
            int totalDaysOfLandedProjectsToShow = settings.Read<int>("TotalDaysOfLandedProjectsToShow", 30);
            foreach (var item in DetailedItems)
            {
                if (item.Status == ProjectStatus.Landed)
                {
                    // get all landed projects
                    landedProjects.Add(item.Id, item.DateLanded);
                }
            }
            if (filterLandedProjectsByCount && landedProjects.Count > totalLandedProjectsToShow)
            {
                int countNow = 0;
                foreach (var item in landedProjects)
                {
                    countNow++;
                    if (countNow > (landedProjects.Count - totalLandedProjectsToShow))
                    {
                        break;
                    }
                    DetailedItems.Remove(DetailedItems.Single(_ => _.Id == item.Key));
                }
            }
            else if (!filterLandedProjectsByCount)
            {
                foreach (var item in landedProjects.Where(o => o.Value.HasValue && DateTime.Today.Subtract(o.Value.Value).TotalDays > totalDaysOfLandedProjectsToShow))
                {
                    DetailedItems.Remove(DetailedItems.Single(_ => _.Id == item.Key));
                }
            }
        }

        /// <summary>
        /// Gets the cached workitems.
        /// </summary>
        private async Task GetCachedWorkitems()
        {
            var file = default(StorageFile);
            var document = default(XDocument);
            try
            {
                file = await ApplicationData.Current.LocalFolder.GetFileAsync("workItems");
            }
            catch (FileNotFoundException)
            {
                return;
            }

            var goodData = false;
            using (var stream = await file.OpenStreamForReadAsync())
            {
                try
                {
                    document = XDocument.Load(stream);
                    goodData = true;
                }
                catch (XmlException) { }
            }

            if (!goodData)
            {
                Debug.WriteLine("Bad data point 1");
                await file.DeleteAsync(StorageDeleteOption.PermanentDelete);
                return;
            }

            LastUpdated = DateTime.Parse(document.Root.Attribute("date").Value, CultureInfo.InvariantCulture);
            var items = document.Descendants("workitem");
            var newItems = new List<DetailedViewItem>();
            goodData = true;
            foreach (var item in items)
            {
                try
                {
                    newItems.Add(WorkItem.FromXML(item));
                }
                catch (FormatException)
                {
                    goodData = false;
                    break;
                }
                catch (NullReferenceException)
                {
                    goodData = false;
                    break;
                }
            }

            if (!goodData)
            {
                await file.DeleteAsync(StorageDeleteOption.PermanentDelete);
                return;
            }

            foreach (var item in newItems)
            {
                allItems.Add(item);
            }

            UpdateDisplayItems();
        }

        /// <summary>
        /// Gets the credentials.
        /// </summary>
        /// <returns></returns>
        private Credentials GetCredentials()
        {
            if (settings == null)
            {
                return null;
            }

            var account = settings.Read<string>("account", null);
            if (account == null)
            {
                return null;
            }

            var username = settings.Read<string>("username", null);
            var password = settings.Read<string>("password", null);

            return new Credentials(account, username, password);
        }

        /// <summary>
        /// Gets the latest work items.
        /// </summary>
        private async Task GetLatestWorkItems()
        {
            Loading = true;
            var workitems = await VisualStudioOnlineOData.WorkItems.GetWorkItemsAsync(credentials, this.QueryId, async s => { await SaveWorkItems(s); });

            foreach (var item in workitems)
            {
                var existingItem = allItems.SingleOrDefault(_ => _.Id == item.Id);
                if (existingItem == null)
                {
                    allItems.Add(item);
                }
                else
                {
                    existingItem.UpdateFrom(item);
                    var uiVersion = DetailedItems.SingleOrDefault(_ => _.Id == item.Id);
                    if (uiVersion != null)
                    {
                        uiVersion.UpdateFrom(item);
                    }
                }
            }

            LastUpdated = DateTime.Now;
            UpdateDisplayItems();
            Loading = false;
        }

        private void OnEditMetadata(object item)
        {
#if WINDOWS_PHONE_APP

#else
            Window.Current.CoreWindow.KeyUp += EditorActiveCoreWindowKeyUp;
            SelectedItem = item as DetailedViewItem;
            MetadataEditor = true;
#endif
        }

        private async Task OnPasswordProvided()
        {
            PasswordPrompt = false;
            await GetLatestWorkItems();
        }

        private async Task OnRefresh()
        {
            await GetLatestWorkItems();
        }

        private async Task OnClearCache()
        {
            try
            {
                var file = await ApplicationData.Current.LocalFolder.GetFileAsync("workItems");
                await file.DeleteAsync(StorageDeleteOption.PermanentDelete);
            }
            catch (FileNotFoundException) { }
            await new MessageDialog("Cache has been cleared, we will now refresh the data.").ShowAsync();
            await OnRefresh();
        }

        private void OnOpenLegend()
        {
            this.ShowLegend = true;
        }

        private void OnCloseLegend()
        {
            this.ShowLegend = false;
        }

        private void OnToggleHiddenItems()
        {
            ShowAllItems = !ShowAllItems;
            UpdateDisplayItems();
        }

        private void OnToggleHideStatus(object item)
        {
            var detailedItem = item as DetailedViewItem;
            detailedItem.Hidden = !detailedItem.Hidden;
            SettingUtils.HiddenItemsContainer().Values[detailedItem.Id] = detailedItem.Hidden;
            CloseEditor();
            UpdateDisplayItems();
        }

        private async Task OnUpdateMetadata(object item)
        {
            CloseEditor();
            ShowUpdating = true;
            var detailedItem = item as DetailedViewItem;
            UpdateMetaDataState(detailedItem);
            var result = WorkItemProperties.UpdatedDescription(detailedItem.Description, detailedItem);
            var success = await WorkItems.Update(detailedItem.Id, credentials, detailedItem.State, result);
            ShowUpdating = false;

            if (!success)
            {
                await new MessageDialog("There was an issue updating the workitem. Please try again.").ShowAsync();
            }
        }

        /// <summary>
        /// Saves the work items.
        /// </summary>
        /// <param name="workItems">The work items.</param>
        private async Task SaveWorkItems(XDocument workItems)
        {
            workItems.Root.Add(new XAttribute("date", DateTime.Now));
            var file = await ApplicationData.Current.LocalFolder.CreateFileAsync("workItems", CreationCollisionOption.ReplaceExisting);
            using (var stream = await file.OpenStreamForWriteAsync())
            {
                workItems.Save(stream);
            }
        }

        private async void TaskCompleted(IBackgroundTaskRegistration task, BackgroundTaskCompletedEventArgs args)
        {
            await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(new CoreDispatcherPriority(), async () =>
            {
                await GetCachedWorkitems();
            });
        }

        private void UpdateMetaDataState(DetailedViewItem item)
        {
            if (item.Scheduled != DateTimeOffsetTools.Base() && item.Scheduled < DateTimeOffset.UtcNow)
            {
                if (item.Status == ProjectStatus.Landed)
                {
                    return;
                }

                item.Status = ProjectStatus.Delayed;
            }
        }
    }
}